package com.jay.audio.soundtouch

class SoundTouch {

    private var handle: Long = 0L
    private var released: Boolean = true

    init {
        handle = newInstance()
        setChannels(handle, 1)
        released = false
    }

    fun release() {
        checkReleased()
        deleteInstance(handle)
        handle = 0
        released = true
    }

    private fun checkReleased() {
        if (released) {
            throw IllegalStateException(
                "The native instance has been released. " +
                        "Please create a new SoundTouch object for use."
            )
        }
    }

    private external fun newInstance(): Long
    private external fun deleteInstance(handle: Long)
    private external fun getChannels(handle: Long): Int
    private external fun setChannels(handle: Long, channels: Int)
    private external fun setSampleRate(handle: Long, srate: Int)
    private external fun getUnprocessedSampleCount(handle: Long): Int
    private external fun getSampleCount(handle: Long): Int
    private external fun adjustAmountOfSamples(handle: Long, amount: Int): Int
    private external fun flush(handle: Long)
    private external fun clear(handle: Long)
    private external fun isEmpty(handle: Long): Int
    private external fun getInputOutputSampleRatio(handle: Long): Double
    private external fun putSamples(handle: Long, samples: ShortArray, amount: Int)
    private external fun receiveSamples1(
        handle: Long,
        sampleBuffer: ShortArray,
        maxSamples: Int
    ): Int

    private external fun receiveSamples2(handle: Long, maxSamples: Int): Int

    private external fun setRate(handle: Long, rate: Double)
    private external fun setTempo(handle: Long, tempo: Double)
    private external fun setRateChange(handle: Long, rate: Double)
    private external fun setTempoChange(handle: Long, tempo: Double)

    private external fun setPitch(handle: Long, pitch: Double)
    private external fun setPitchOctaves(handle: Long, pitch: Double)
    private external fun setPitchSemiTones1(handle: Long, pitch: Int)
    private external fun setPitchSemiTones2(handle: Long, pitch: Double)

    private external fun setSettingValue(handle: Long, settingId: Int, value: Int): Int
    private external fun getSettingValue(handle: Long, settingId: Int): Int

    companion object {

        /**
         * SoundTouch library version string.
         */
        private const val SOUNDTOUCH_VERSION = "2.2"

        /**
         * SoundTouch library version id.
         */
        private const val SOUNDTOUCH_VERSION_ID = 20200

        /**
         * Max allowed number of channels.
         */
        private const val SOUNDTOUCH_MAX_CHANNELS = 16

        // Available setting IDs for the 'setSettingValue' & 'getSettingValue' functions:

        // Available setting IDs for the 'setSettingValue' & 'getSettingValue' functions:
        /**
         * Enable/disable anti-alias filter in pitch transposer (0 = disable).
         */
        const val SETTING_USE_AA_FILTER = 0

        /**
         * Pitch transposer anti-alias filter length (8 .. 128 taps, default = 32).
         */
        const val SETTING_AA_FILTER_LENGTH = 1

        /**
         * Enable/disable quick seeking algorithm in tempo changer routine
         * (enabling quick seeking lowers CPU utilization but causes a minor sound
         * quality compromising).
         */
        const val SETTING_USE_QUICKSEEK = 2

        /**
         * Time-stretch algorithm single processing sequence length in milliseconds. This determines
         * to how long sequences the original sound is chopped in the time-stretch algorithm.
         */
        const val SETTING_SEQUENCE_MS = 3

        /**
         * Time-stretch algorithm seeking window length in milliseconds for algorithm that finds the
         * best possible overlapping location. This determines from how wide window the algorithm
         * may look for an optimal joining location when mixing the sound sequences back together.
         */
        const val SETTING_SEEKWINDOW_MS = 4

        /**
         * Time-stretch algorithm overlap length in milliseconds. When the chopped sound sequences
         * are mixed back together, to form a continuous sound stream, this parameter defines over
         * how long period the two consecutive sequences are let to overlap each other.
         */
        const val SETTING_OVERLAP_MS = 5

        /**
         * Call "getSetting" with this ID to query processing sequence size in samples.
         * This value gives approximate value of how many input samples you'll need to
         * feed into SoundTouch after initial buffering to get out a new batch of
         * output samples.
         *
         *
         * This value does not include initial buffering at beginning of a new processing
         * stream, use SETTING_INITIAL_LATENCY to get the initial buffering size.
         *
         *
         * Notices:
         * - This is read-only parameter, i.e. setSetting ignores this parameter
         * - This parameter value is not constant but change depending on
         * tempo/pitch/rate/samplerate settings.
         */
        const val SETTING_NOMINAL_INPUT_SEQUENCE = 6

        /**
         * Call "getSetting" with this ID to query nominal average processing output
         * size in samples. This value tells approcimate value how many output samples
         * SoundTouch outputs once it does DSP processing run for a batch of input samples.
         *
         *
         * Notices:
         * - This is read-only parameter, i.e. setSetting ignores this parameter
         * - This parameter value is not constant but change depending on
         * tempo/pitch/rate/samplerate settings.
         */
        const val SETTING_NOMINAL_OUTPUT_SEQUENCE = 7

        /**
         * Call "getSetting" with this ID to query initial processing latency, i.e.
         * approx. how many samples you'll need to enter to SoundTouch pipeline before
         * you can expect to get first batch of ready output samples out.
         *
         *
         * After the first output batch, you can then expect to get approx.
         * SETTING_NOMINAL_OUTPUT_SEQUENCE ready samples out for every
         * SETTING_NOMINAL_INPUT_SEQUENCE samples that you enter into SoundTouch.
         *
         *
         * Example:
         * processing with parameter -tempo=5
         * => initial latency = 5509 samples
         * input sequence  = 4167 samples
         * output sequence = 3969 samples
         *
         *
         * Accordingly, you can expect to feed in approx. 5509 samples at beginning of
         * the stream, and then you'll get out the first 3969 samples. After that, for
         * every approx. 4167 samples that you'll put in, you'll receive again approx.
         * 3969 samples out.
         *
         *
         * This also means that average latency during stream processing is
         * INITIAL_LATENCY-OUTPUT_SEQUENCE/2, in the above example case 5509-3969/2
         * = 3524 samples
         *
         *
         * Notices:
         * - This is read-only parameter, i.e. setSetting ignores this parameter
         * - This parameter value is not constant but change depending on
         * tempo/pitch/rate/samplerate settings.
         */
        const val SETTING_INITIAL_LATENCY = 8


        // Used to load the 'soundtouch' library on application startup.
        init {
            System.loadLibrary("soundtouch")
        }

        fun getMaxChannels(): Int {
            return SOUNDTOUCH_MAX_CHANNELS
        }
    }
}